iT邦幫忙

2026 iThome 鐵人賽

DAY 21
0
Software Development

從零打造邊緣運算閘道器:Raspberry Pi 與 Linux 底層軟硬整合實戰系列 第 21

Day 21 - 軟即時架構 (Soft Real-Time):自己當交通警察,打造搶佔式任務排程

  • 分享至 

  • xImage
  •  

在上一篇文章中,我們看清了殘酷的現實:如果讓系統中的各個執行緒 (Threads) 各自為政、直接操作 GPIO 控制 LED 燈號,最終只會演變成一場資源爭奪的災難。互斥鎖 (Lock) 雖然能防止混色,卻會導致重要的警告燈號被低優先級的動畫給阻塞。

為了解決這個問題,我們將引入「軟即時 (Soft Real-Time)」系統的概念。

[底層原理] 什麼是軟即時架構?

在純硬體的即時作業系統 (RTOS) 中,系統保證高優先級的任務能夠在微秒級別的時間內「搶佔 (Preempt)」低優先級的任務。Linux 雖然不是即時作業系統,但我們可以在 Python 應用程式內部,實作一個類似的排程機制。

我們的核心理念是:解耦與集權
將原本分散在各處的 LED 控制邏輯拔除,統一交由一個獨立的 LEDController 執行緒來管理。

這就像一個交通十字路口,各個執行緒就像是一輛輛汽車,而 LEDController 就是那個站在路中間指揮交通的警察。

打造搶佔式排程器 (Preemptive Scheduler)

我們需要一個狀態機機制,讓這個唯一的交通警察能夠評估當前的局勢,並決定現在應該亮什麼燈。

第一步:定義「動畫任務」

我們不再只是單純地給予一個顏色,而是將每一種燈號行為定義為一個動畫函式 (Animation Function)

# animations.py
import time

def breathe_green(led_hardware, check_cancel):
    while not check_cancel():
        # 實作綠燈漸亮
        led_hardware.set_pwm(GREEN, intensity)
        time.sleep(0.05)
        # 實作綠燈漸暗...

def blink_blue(led_hardware, check_cancel):
    while not check_cancel():
        led_hardware.set_color(BLUE)
        time.sleep(0.5)
        if check_cancel(): break
        led_hardware.set_color(BLACK)
        time.sleep(0.5)

[!IMPORTANT]
注意到這裡多了一個神秘的參數:check_cancel。這是一個回呼函式 (Callback),每次迴圈或睡眠後,動畫都必須呼叫它。如果它回傳 True,動畫就必須立刻終止 break。這是我們實現「搶佔」的基石!

第二步:建立集中式的 LEDController

接下來,我們建立這位交通警察。他擁有一個內部的佇列 (Queue) 或者直接記錄當前的「請求」。

# led_controller.py
import threading

class LEDController:
    def __init__(self, hardware):
        self.hw = hardware
        self._current_anim_thread = None
        self._cancel_flag = False
        
    def _run_anim(self, anim_func):
        # 將檢查旗標的函式傳給動畫
        anim_func(self.hw, lambda: self._cancel_flag)
        
    def request_animation(self, anim_func):
        # 1. 停止目前的動畫 (發送中斷訊號)
        if self._current_anim_thread and self._current_anim_thread.is_alive():
            self._cancel_flag = True
            self._current_anim_thread.join() # 等待舊動畫乖乖結束
            
        # 2. 重置中斷旗標,準備啟動新動畫
        self._cancel_flag = False
        
        # 3. 啟動新動畫
        self._current_anim_thread = threading.Thread(
            target=self._run_anim, 
            args=(anim_func,)
        )
        self._current_anim_thread.start()

[最終解決方案] 隨時隨地,安全換燈

現在,其他執行緒再也不需要直接碰觸硬體了。當 SyncEngine 發現有新資料傳入時,它只需要做一件事:

# 在 SyncEngine 內部
led_controller.request_animation(blink_blue)

發生了什麼事?

  1. LEDController 收到請求。
  2. 它發現目前正在跑 breathe_green
  3. 它立刻將 _cancel_flag 設為 True
  4. 正在微小睡眠迴圈中的 breathe_green 醒來,檢查 check_cancel() 發現變為 True,立刻 break 退出。
  5. 舊動畫乾淨俐落地結束,LEDController 隨即啟動 blink_blue 動畫。

這就是我們的軟體搶佔機制!
沒有混色、沒有互相踩踏。新的指令總是能迅速且乾淨地取代舊的指令。

但等等,這個架構還缺了一塊非常致命的拼圖。如果現在正在「閃紅燈(致命錯誤)」,而有一個不知好歹的背景執行緒突然發送了「待機綠燈」的請求,紅燈不就被綠燈搶佔覆蓋了嗎!?

沒錯,交通警察需要能夠分辨「長官座車」與「一般民車」的能力。明天,我們將在 Day 22 探討如何為這套架構加入優先權設計 (Priority Design)世代計數器 (Generation Counter),徹底完善這個系統!


上一篇
Day 20 - 多執行緒的混亂:為何 Linux 內建的 CPU 排程器不適合用來控制 LED 燈號?
下一篇
Day 22 - 優先權設計:如何確保「致命錯誤」的紅燈永遠不會被「待機」的綠燈覆蓋?
系列文
從零打造邊緣運算閘道器:Raspberry Pi 與 Linux 底層軟硬整合實戰26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言